package com.qf.filter;

import com.alibaba.fastjson.JSON;
import com.qf.pojo.Result;
import com.qf.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * 这个过滤器默认处理的是session中是否有当前用户的信息，如果有就证明登录。没有就证明没登陆
 * 需求：验证浏览器端通过请求头携带过来的token信息
 */
public class JwtVerifyFilter extends BasicAuthenticationFilter {

    public JwtVerifyFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    /**
     * 这个方法默认逻辑是校验session中的username信息
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        //获取请求头中的token
        String token = request.getHeader("token");
        if (token == null || token.equals("null")){
            //这个资源不需要登录认证。或者需要登录认证，但是没有携带
            chain.doFilter(request,response);
            return;
        }

        try {
            Claims claims = JwtUtils.parseBody(token);

            //证明是登录状态（对于/user/isLogin这个资源而言，不需要做其他的业务，只需要告诉前端当前的登录状态即可）
            String uri = request.getRequestURI();
            if (uri.contains("/user/isLogin")){
                //是这个资源，返回前端即可
                Result success = Result.success(null);
                response.getWriter().write(JSON.toJSONString(success));
                return;
            }


            //除了/user/isLogin之外的其他资源都需要做业务的，所以需要继续向下放行
            setContext(claims);
            //放行
            chain.doFilter(request,response);
        }catch (ExpiredJwtException expiredJwtException){
            //面试题
            //jwt过期了  todo: jwt过期了怎么办？？？
            Claims claims = expiredJwtException.getClaims();
            long expireMillions = claims.getExpiration().getTime();
            long nowMillions = System.currentTimeMillis();

            if (nowMillions - expireMillions > 1000*60*60){
                //过期时间太久了,重新登录
                Result result = Result.error(Result.ERROR, "认证信息过期了");

                String json = JSON.toJSONString(result);

                response.setContentType("text/html;charset=utf-8");
                response.getWriter().write(json);
            }else{
                //过期时间比较短，允许复活
                //重新生成jwt
                String newToken = JwtUtils.createToken(claims, claims.getSubject());
                //将重新生成的jwt通过响应头的方式，传递给浏览器（前端将这个值拿到后，将原本存储的token覆盖即可）
                response.addHeader("refresh_token",newToken);

                //将用户信息保存到security的上下文中
                setContext(claims);
                //放行
                chain.doFilter(request,response);
            }

        }catch (Exception e){
            //jwt非法(签名不正确或者jwt被篡改)
            Result result = Result.error(Result.ERROR, "认证信息非法");

            String json = JSON.toJSONString(result);

            response.setContentType("text/html;charset=utf-8");
            response.getWriter().write(json);
        }
    }

    private void setContext(Claims claims) {
        String username = claims.getSubject();
        List list = claims.get("roles", List.class);
        Collection authorities = new ArrayList<>();

        for (Object o : list) {
            GrantedAuthority authority = new SimpleGrantedAuthority((String) o);
            authorities.add(authority);
        }

        //将用户的信息存入security上下文中
        Authentication authentication = new UsernamePasswordAuthenticationToken(username,null,authorities);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
}
